package com.stark.wechat.service.material;

import com.dtflys.forest.Forest;
import com.dtflys.forest.backend.ContentType;
import com.dtflys.forest.http.ForestResponse;
import com.dtflys.forest.multipart.ByteArrayMultipart;
import com.fasterxml.jackson.databind.JsonNode;
import com.stark.wechat.core.Config;
import com.stark.wechat.core.File;
import com.stark.wechat.exception.WechatException;
import com.stark.wechat.service.material.model.MaterialType;
import com.stark.wechat.service.material.model.UploadTempMediaResponsePayload;
import com.stark.wechat.util.JacksonUtils;
import com.stark.wechat.util.ResponseUtils;
import lombok.Builder;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
 * 临时素材接口
 *
 * @author <a href="mailto:mengbin@hotmail.com">Ben</a>
 * @version 1.0.0
 * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/temporary/api_getmedia.html">素材管理 / 临时素材 / 获取临时素材</a>
 * @since 2024/5/24
 */
@Builder
public class TemporaryMaterialService {

    private final Config config;

    /**
     * 获取临时素材
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param mediaId     媒体文件ID
     * @return 临时素材信息，视频类型返回下载链接(String)，其他类型返回文件(File)
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/temporary/api_getmedia.html">素材管理 / 临时素材 / 获取临时素材</a>
     */
    public Object getMedia(String accessToken, String mediaId) {
        requireNonNull(accessToken);
        requireNonNull(mediaId);

        @SuppressWarnings("rawtypes")
        ForestResponse response = Forest
                .get(config.getWechatGateway() + "/cgi-bin/media/get")
                .addQuery("access_token", accessToken)
                .addQuery("media_id", mediaId)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsResponse();
        ContentType contentType = response.getContentType();
        // 文件
        if (contentType != null) {
            String type = contentType.getType();
            if ("image".equalsIgnoreCase(type)
                    || "audio".equalsIgnoreCase(type)
                    || "video".equalsIgnoreCase(type)) {
                String contentDisposition = response.getHeader("content-disposition").getValue();
                String filename = contentDisposition.contains("filename=")
                        ? StringUtils.substringBetween(contentDisposition, "filename=\"", "\"")
                        : "anonymous." + StringUtils.substringAfterLast(contentType.toString(), "/").toLowerCase();
                try {
                    return new File(response.getByteArray(), filename, contentType.toString());
                } catch (Exception e) {
                    throw new WechatException("读取文件流失败", e);
                }
            }
        }
        // json
        String json = response.getContent();
        JsonNode root = null;
        try {
            root = JacksonUtils.readTree(json);
        } catch (Exception e) {
            // do nothing
        }
        if (root != null) {
            ResponseUtils.findError("获取临时素材", root, CODE_MAP.get("getMedia"));
            if (root.has("video_url")) {
                return root.get("video_url").asText();
            }
        }
        // 无法解析的媒体类型
        throw new WechatException("无法解析的媒体类型: " + contentType);
    }

    /**
     * 新增临时素材
     * <p><p>注意事项
     * <ol>
     *     <li>文件大小限制：图片2MB/视频10MB</li>
     *     <li>媒体文件保存3天</li>
     * </ol>
     * <p><p>其他补充
     * <ol>
     *     <li>临时素材media_id是可复用的。</li>
     *     <li>媒体文件在微信后台保存时间为3天，即3天后media_id失效。</li>
     *     <li>上传临时素材的格式、大小限制与公众平台官网一致。</li>
     *     <li>图片（image）: 10M，支持PNG\JPEG\JPG\GIF格式</li>
     *     <li>语音（voice）：2M，播放长度不超过60s，支持AMR\MP3格式</li>
     *     <li>视频（video）：10MB，支持MP4格式</li>
     *     <li>缩略图（thumb）：64KB，支持JPG格式</li>
     * </ol>
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param type        媒体文件类型
     * @param media       媒体文件
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/temporary/api_uploadtempmedia.html">素材管理 / 临时素材 / 新增临时素材</a>
     */
    public UploadTempMediaResponsePayload uploadTempMedia(String accessToken, MaterialType type, File media) {
        requireNonNull(accessToken);
        requireNonNull(type);
        requireNonNull(media);

        ByteArrayMultipart file = new ByteArrayMultipart()
                .setData(media.getBytes())
                .setName("media")
                .setFileName(media.getFilename())
                .setContentType(media.getContentType());

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/media/upload")
                .contentTypeMultipartFormData()
                .addQuery("access_token", accessToken)
                .addQuery("type", type)
                .addMultipart(file)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("新增临时素材", root, CODE_MAP.get("uploadTempMedia"));
        return JacksonUtils.deserialize(responseStr, UploadTempMediaResponsePayload.class);
    }

    /**
     * 获取高清语音素材
     * <p>本接口用于获取从 JSSDK 的 uploadVoice 接口上传的临时语音素材，格式为speex，16K采样率。该音频比临时素材获取接口（格式为amr，8K采样率）更加清晰，适合用作语音识别等对音质要求较高的业务
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param mediaId     媒体文件ID
     * @return 格式为speex，16K采样率的高清语音素材
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/temporary/api_uploadtempmedia.html">素材管理 / 临时素材 / 新增临时素材</a>
     */
    public File getHDVoice(String accessToken, String mediaId) {
        requireNonNull(accessToken);
        requireNonNull(mediaId);

        @SuppressWarnings("rawtypes")
        ForestResponse response = Forest
                .get(config.getWechatGateway() + "/cgi-bin/media/get/jssdk")
                .addQuery("access_token", accessToken)
                .addQuery("media_id", mediaId)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsResponse();
        ContentType contentType = response.getContentType();
        // 音频文件
        if (contentType.toString().startsWith("audio/")) {
            String contentDisposition = response.getHeader("content-disposition").getValue();
            String filename = contentDisposition.contains("filename=")
                    ? StringUtils.substringBetween(contentDisposition, "filename=\"", "\"")
                    : "anonymous." + StringUtils.substringAfterLast(contentType.toString(), "/").toLowerCase();
            try {
                return new File(response.getByteArray(), filename, contentType.toString());
            } catch (Exception e) {
                throw new WechatException("读取文件流失败", e);
            }
        }
        // json
        if (ContentType.APPLICATION_JSON.equals(contentType.toString())) {
            String json = response.getContent();
            JsonNode root = JacksonUtils.readTree(json);
            ResponseUtils.findError("获取高清语音素材", root, CODE_MAP.get("getHDVoice"));
        }
        throw new WechatException("无法解析的媒体类型: " + contentType);
    }

    private static final Map<String, Map<Long, String>> CODE_MAP;

    static {
        CODE_MAP = new HashMap<>();

        Map<Long, String> codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40007L, "无效的媒体ID");
        CODE_MAP.put("getMedia", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40004L, "不合法的媒体文件类型");
        CODE_MAP.put("uploadTempMedia", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(40007L, "无效的媒体ID");
        CODE_MAP.put("getHDVoice", Collections.unmodifiableMap(codeMap));
    }

}
